package com.luke.xiaotiaowa.aspect;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.luke.xiaotiaowa.aspect.annotation.ExclusionLog;
import com.luke.xiaotiaowa.modules.system.entity.AdminAccessLog;
import com.luke.xiaotiaowa.modules.system.service.AdminAccessLogService;
import com.luke.xiaotiaowa.utils.JwtTokenUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Optional;

/**
 * 访问日志处理
 * 
 * @author xiaotiaowa
 * @create 2023/3/30
 */
@Slf4j
@Aspect
@Component
@Order(1)
public class WebsiteAccessLogAspect {

    @Resource
    private JwtTokenUtil jwtTokenUtil;
    @Resource
    private AdminAccessLogService accessLogService;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Pointcut("execution(public * com.luke.xiaotiaowa.controller.*.*(..))")
    public void websiteAccessLogAspect() {
    }

    @Before("websiteAccessLogAspect()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }

    @AfterReturning(value = "websiteAccessLogAspect()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }

    @Around("websiteAccessLogAspect()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        var now = LocalDateTime.now();
        //获取当前请求对象
        var attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        var request = attributes.getRequest();
        var signature = joinPoint.getSignature();
        var methodSignature = (MethodSignature) signature;
        var method = methodSignature.getMethod();
        // 返回结果
        Object result = null;
        // 日志对象
        var accessLog = buildAccessLog(request, method, joinPoint, now);
        try {
            result = joinPoint.proceed();

            accessLog.setResult(result);
        } catch (Exception e) {
            log.error("websiteAccessLogAspect err ", e);
            accessLog.setStatus(false);
            accessLog.setResult(e.getMessage());
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            accessLog.setSpendTime((int) (endTime - startTime));
            try {
                if (!method.isAnnotationPresent(ExclusionLog.class)) {
                    accessLogService.save(accessLog);
                }
            } catch (Exception e) {
                log.error("save mongoDB err ", e);
            }
        }
        log.info("{}", JSONUtil.parse(accessLog));
        return result;
    }
    
    /**
     * 解析请求参数
     * @param method 方法
     * @param args 入参
     * @return Object
     */
    private Object getParameter(Method method, Object[] args) {
        var argList = new ArrayList<>();
        var parameters = method.getParameters();
        for (var i = 0; i < parameters.length; i++) {
            if (parameters[i].getType().equals(MultipartFile.class)) {
                continue;
            }
            var requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (ObjectUtil.isNotNull(requestBody)) {
                argList.add(args[i]);
            }
            var requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (ObjectUtil.isNotNull(requestParam)) {
                var map = new HashMap<String, Object>();
                var key = parameters[i].getName();
                if (StrUtil.isNotEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        return Optional.of(argList)
                .filter(v -> v.size() > 0)
                .map(v -> argList)
                .orElse(null);
    }
    
    private AdminAccessLog buildAccessLog(HttpServletRequest request, Method method, ProceedingJoinPoint joinPoint,
                                          LocalDateTime now) {
        var accessLog = new AdminAccessLog();
        var url = request.getRequestURL().toString();
        accessLog.setUsername(jwtTokenUtil.getUserNameFromToken(request.getHeader(this.tokenHeader).replace("Bearer ", "")));
        // 请求参数
        accessLog.setParameter(getParameter(method, joinPoint.getArgs()));
        if (method.isAnnotationPresent(ApiOperation.class)) {
            var apiOperation = method.getAnnotation(ApiOperation.class);
            accessLog.setDescription(apiOperation.value());
        }
        accessLog.setBasePath(StrUtil.removeSuffix(url, URLUtil.url(url).getPath()));
        accessLog.setIp(ServletUtil.getClientIP(request));
        accessLog.setMethod(request.getMethod());
        accessLog.setStartTime(now);
        accessLog.setUri(request.getRequestURI());
        accessLog.setStatus(true);
        return accessLog;
    }
}
